// ==UserScript==		
// @name		Help Forum Tweaks
// @namespace   http://google-help-forum-tweaks.googlecode.com/svn/trunk/help-forum-tweaks.user.js
// @description	Tweaks for the Google Help Forums
// @version	 0.79
// @include	 http://www.google.com/support/forum/p/*
// @include	 http://www.google.de/support/forum/p/*
// @include	 http://www.google.at/support/forum/p/*
// @include	 http://www.google.ch/support/forum/p/*
// @include	 http://www.google.it/support/forum/p/*
// @include	 http://www.google.co.uk/support/forum/p/*
// @include	 http://www.google.es/support/forum/p/*
// @include	 http://www.google.*/support/forum/user*
// @include	 http://www.google.*/support/forum/p/*/user
// ==/UserScript==

/*
Copyright 2009 - 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/* Requires Chrome >= 2.x with user-scripts enabled 
** or Mozilla FireFox with Greasemonkey Extensions.
**
** See http://dev.chromium.org/developers/design-documents/user-scripts
** or http://lifehacker.com/5180010/enable-user-scripts-in-google-chrome
**
** Make sure to put it in the right location, eg:
** C:\Documents and Settings\yourname\Local Settings\Application Data\
**	 Google\Chrome\User Data\Default\User Scripts
** and restart Chrome with the option " --enable-user-scripts"
**
** SVN information (please _don't_ remove next line):
** $Id: help-forum-tweaks.user.js 80 2011-04-09 13:06:52Z theylmdl $
**
*/

var URL = window.location.href, ghfConfig = new Object, languageCode = ""; 
var thedomain = window.location.protocol + "//" + window.location.host;

// Google Help Forum view / tweak configuration.
// Feel free to adjust the settings to your personal preferences.
// Remember to reinstall script in Chrome when making changes. Firefox requires only a page reload.
function setGHFConfig() {
	// Threads marked as visited in your browser.
	ghfConfig.color_read_by_you	= "#E5E5E5";
	// Threads marked as _not_ visited in your browser.
	ghfConfig.color_read_by_others = "#FFFFFF" ; "#E7EFFF";
	// Title font size. 13px defaults and doesn't hurt the layout, 15px is already large.
	ghfConfig.title_font_size = "13px";
	// "true" or "false" to show thread titles in bold font.
	ghfConfig.thread_title_bold = false;
	// 1 or 0 to show user votes in bold font. Note this is 0/1 to add further options.
	ghfConfig.votes_bold = 1;
	// "true" or "false" to show side links to boards (a menu).
	ghfConfig.show_side_links = true;
	// "true" or "false" to resize the edit field area for posts.
	ghfConfig.resize_editor = true;
	// "true" or "false" to fix the injected and default left hand menu in the viewport.
	ghfConfig.fixed_menu = true;
	// "true" or "false" to display last post dates as links.
	ghfConfig.link_to_last = true;
	// "true" or "false" to tweak votes with less thumbs up than participants, 
	// applies only to some languages by now.
	ghfConfig.tweak_votes = true;
	// "true" or "false". When true, a permalink will be written under each post beneath "report abuse".
	ghfConfig.show_permalinks = true;
}

// Cookie set and get functions.
if (!GM_getValue && !GM_setValue && !unsafeWindow) {
	function GM_setValue(cookieName, cookieValue, lifeTime) {
		if (!cookieName) { return; }
		if (lifeTime == "delete") { lifeTime = -10; }
		else { lifeTime = 31536000; }
		document.cookie = escape(cookieName) + "=" + escape(cookieValue) +
						  ";expires=" + (new Date((new Date()).getTime() + (1000 * lifeTime))).toGMTString() + ";path=/";
	}

	function GM_getValue(cookieName, oDefault) {
		var cookieJar = document.cookie.split("; ");
		for (var x = 0; x < cookieJar.length; x++) {
			var oneCookie = cookieJar[x].split("=");
			if (oneCookie[0] == escape(cookieName)) {
				try	{ var footm = unescape(oneCookie[1]); } 
				catch(e) { return oDefault; }
				return footm;
			}
		}
		return oDefault;
	}

	window.unsafeWindow = window;
}

// Get us the forum name either from the current URL or the referrer.
function getForumName() {
	var forumName = szReferrer = "";
	try {
		forumName = URL.match("/forum\/p\/([^/]*)[/\?]")[1];
	} catch(e) {
		if (document.referrer && document.referrer.length) {
			szReferrer = document.referrer;
			try {
				forumName = szReferrer.match("/forum\/p\/([^/]*)[/\?]")[1];
			} catch(e) {
				return "";
			}
		}
	}
	return forumName;
}

// Show the links in the sidebar
function showSideLinks(serialized_data) {
	var entries = serialized_data.split("\t"), forumName = "";
	var side_ul = document.getElementById("wparoot");
	var new_li, new_a, i = 0;
	if (side_ul) {
		for (i = 0; i < entries.length; i++) {
			var suburl = entries[i].match(/\"(.*?)\"/);
			var subtitle = entries[i].match(/>(.*?)</);
			var subclass = entries[i].match(/class="(.*?)"/);
			if (!subclass) {
				new_li = document.createElement("li");
				new_li.id = "new_li_" + i;
				new_li.className = "wpabaruhCSS";
				new_a = document.createElement("a");
				new_a.href = suburl[1];
				new_a.innerHTML = subtitle[1];
				new_li.appendChild(new_a);
				side_ul.appendChild(new_li);
			}
		}

		// Add a link to the thread overview on any other page.
		if (!URL.match("browse")) {
			forumName = getForumName();
			if (!forumName || 0 === forumName.length) {
				return;
			}
			new_li = document.createElement("li");
			new_li.id = "new_li_" + (i + 1);
			new_li.className = "wpabaruhCSS";
			new_a = document.createElement("a");
			new_a.href = thedomain + "/support/forum/p/" + forumName + "/browse?hl=" + languageCode;
			switch (languageCode) {
				case "de": new_a.innerHTML = "Alle Threads"; break;
				case "it": new_a.innerHTML = "Tutte le discussioni"; break;
				case "fr": new_a.innerHTML = "Toutes les discussions"; break;
				case "es": new_a.innerHTML = "Todas las discussiónes"; break;
				default:   new_a.innerHTML = "All Threads"; // default
			}
			new_li.appendChild(new_a);
			side_ul.appendChild(new_li);
		}
	}
}

// Fix editor window: make larger, fix horizontal scrolling
function fixEditorTextField() {
	// Editor is made up of multiple frames, we must fix two of them
	try {
		if (document.getElementById("wmfroot")) {
			// on thread page, change size of editor div to double height
			var newCSS = "div#wpiprde.wpiprdeCSS { height: 300px !important; }";
			GM_addStyle(newCSS);
			// for non-richtext editor users, adjust row count in text area
			var newCSS = "textarea#wpiprdi { height: 300px !important; }";
			GM_addStyle(newCSS);
		} else if (document.body.id=="wpiprde") {
			// in the editor page (iframed), update CSS for fixed word wrap
			var newCSS = 'body#wpiprde.editable { width: 100% !important; }';
			GM_addStyle(newCSS);
		}
	} catch (e) { }
}

// Converts the last post's date to a link to this post in the forum overview page. 
// Refactored so links will now work with multiple pages threads.
function addLinkToLastPost() {
	var i = n = nTds = nAnswers = nPages = nOffset = 0, rows = tds = new Array(), row, tmp1, tmp2, cTD = cChild = null, szThreadURL = szPageArg = "";
	try {
		if (document.getElementById("wlhd")) {
			// Traverse table, extract link to post, add to last answer date. 
			// The "my discussions" table (user profile) exists with 5 and 6 columns. 
			// Trap /forum/p/webmasters/user?userid=, too
			if (URL.match(/forum\/(p\/[a-z\+]+?\/)?user/)) {
				for (i = 0; i < document.getElementsByTagName("th").length; i++) {
					cTD = document.getElementsByTagName("th")[i];
					if (180 == cTD.width) {
						nOffset = 1;
						break;
					}
				}
			}
			rows = document.getElementsByTagName("tr");
			for (i = 0; i < rows.length; i++) {
				row = rows[i];
				tds = row.getElementsByTagName("td");
				nTds = tds.length;
				if (4 <= nTds && 6 >= nTds) { // Get thread URL.
//					tmp1 = row.innerHTML.match("\/support\/forum\/p\/[a-zA-Z0-9 \+]*?\/thread.*?\"");
					tmp1 = row.innerHTML.match("\/support\/forum\/p\/[^\/]+\/thread.*?\"");
					if (tmp1) {
						nAnswers = 0;
						cTD = tds[nTds - 2 - nOffset]; // Count of answers.
						if (cTD.innerHTML && cTD.className.match(/^lscCSS/)) {
							tmp2 = cTD.innerHTML.match(/[0-9]{1,4}/);
							if (tmp2) {
								nAnswers = Number(tmp2);
								if (40 >= nAnswers) {
									szPageArg = "";
								} else {
									nPages = Math.floor(nAnswers / 40);
									if (nAnswers === (nPages * 40)) { nPages--; };
									// page 2 has the argument 'start=40', page 3 'start=80' etc.
									szPageArg = "&amp;start=" + (nPages * 40); // page start
								}
							}
						}
						// If there's no reply yet, we don't need a link there - skip it.
						if (0 == nAnswers) { continue; }

						szThreadURL = tmp1[0].substring(0, tmp1[0].length - 1).replace("&amp;","&");
						szThreadURL += szPageArg;
						cTD = tds[nTds - 1 - nOffset];
						// Modified after R 65: Now only the content of the first <div> will be a link, not the whole cell's content.
						for (n = 0; n < cTD.childNodes.length; n++) { // Get first div (date).
							cChild = cTD.childNodes[n];
							if ("DIV" == cChild.tagName || "div" == cChild.tagName) {
								cChild.innerHTML = "<a href=\"" + szThreadURL + "#all\">" + cChild.innerHTML + "</a>";
								break;
							}
						}
					}
				}
			} 
		}
	} catch (e) { }
}

// This function replaces some of the logic before release 0.74.
// Thread pages will now be traversed anyhow under certain conditions and we will decide in place which tweaks to apply or not. 
// The advantage is we'll have to traverse pages only once and much of the "what is what and where" logic will be saved.
function tweakThread() {
	var i = 0, j = 0, cDiv = cChild = cVotes = cNewLnk = cNewTxt = null, fid = szURL = szBaseURL = sz = rExp = szLinkText = hash = params = "";
	var aReplies = new Array();
	var bIsChrome = !!(navigator.userAgent.match(/Chrome\//i));
	// This function shall only be applied to threads, not overviews. Try to find a reply form textarea to check this.
	if (!document.getElementById("wpiprdi") && !document.getElementById("wpcpfhd")) { return; }

	// Determine the base URL. This should be something like:
	// http://www.google.[com|ccTLD|ccTLD.com|co[m].ccTLD]/support/forum/p/*/thread (thread invoked) where * is the internal forum name
	if (-1 === URL.search(/thread\?/)) { return; } // nothing to be done.
	var aTmp = URL.split("?");
	szBaseURL = aTmp[0]; // I.e., http://www.google.com/support/forum/p/webmasters/thread

	// Fix edit input textarea height.
	if (true === ghfConfig.resize_editor) { fixEditorTextField(); }

	// Walk replies.
	for (i = 0; i < document.getElementsByTagName("div").length; i++) {
		cDiv = document.getElementsByTagName("div")[i];

		if (cDiv.className.match(/^wpcpraCSS/)) { // Reply's outer div className, id should be fid_(replyid)
			if (cDiv.id.match(/^fid_/)) {
				fid = cDiv.id.match(/^fid_(.*)/)[1]; // i.e. '09ceaaf0c6b808fe00049e3adca9c10f'
				if (!fid || 32 !== fid.length) { continue; }
				aReplies.push(cDiv);
				// We now need a pointer to the child div with the className "wpcprsCSS".
				cChild = null;
				for (j = (i + 1); j < document.getElementsByTagName("div").length; j++) {
					cChild = document.getElementsByTagName("div")[j];
					if (cChild.className.match(/^wpcprsCSS/)) { break; } // Got it.
				}
				if (null === cChild) { continue; } // This should be present with each reply.

				if (true === ghfConfig.tweak_votes && ("de" === languageCode || "it" === languageCode || "en" === languageCode)) {
					if (cChild.childNodes) {
						cVotes = null;
						// Case 1: no "best answer" button (this may have several reasons).
						if (cChild.childNodes[1]) {
							if (cChild.childNodes[1].childNodes[1] &&
								cChild.childNodes[1].childNodes[1].className && 
								cChild.childNodes[1].childNodes[1].className.match(/^wrwmCSS/)) {
								cVotes = cChild.childNodes[1].childNodes[1];
							}
						}
						// Case 2: A "best answer" form / button ahead.
						if (!cVotes && cChild.childNodes[1] && 'FORM' === cChild.childNodes[1].tagName) {
							if (cChild.childNodes[3] && cChild.childNodes[3].className && 
								cChild.childNodes[3].className.match(/^wrwCSS/)) {
								try {
									cVotes = cChild.childNodes[3].childNodes[1];
									if (cVotes.className.match(/^wrwmCSS/)) { ; }
									else { cVotes = null; }
								} catch(e) { };
							}
						}

						if (cVotes && cVotes.innerHTML && 10 < cVotes.innerHTML.length) {
							sz = cVotes.innerHTML;
							rExp = /([0-9]{1,3}) ([^ ]+?) ([0-9]{1,3}) /;
							rExp.exec(sz);
							try {
								if (RegExp.$3 && RegExp.$1 <= RegExp.$3) { // lte positive votes 
									// Modify the question.
									switch (languageCode) {
										case "de": szLinkText = sz.replace(/Sie auch\?/, "War sie es?"); break;
										case "it": szLinkText = sz.replace(/Anche tu\?/, "Era utile?"); break;
										case "en": szLinkText = sz.replace(/Did you\?/, "Was it?"); break;
									}
									cVotes.innerHTML = szLinkText; // Works only on replies!
								}
							} catch(e) { }
						}

					}
				}

				// Add a permalink for the reply.
				if (true === ghfConfig.show_permalinks) {
					szURL = szBaseURL + "?fid=" + fid + "&hl=" + languageCode; // This is a functional permalink URI including language code.
					cNewTxt = document.createTextNode(" ");
					cChild.appendChild(cNewTxt);
					cNewLnk = document.createElement("a");
					cNewLnk.href = szURL;
					cNewLnk.className = "wpcpirbCSS lCSS"; // Same as "report abuse"
					cNewTxt = document.createTextNode("Permalink");
					cNewLnk.appendChild(cNewTxt);
					cChild.appendChild(cNewLnk); // Add the permalink.
				}
			}
		}
	}

	if (0 === aReplies.length) { return; } // no replies ;-) .
	cDiv = aReplies.pop(); // Last reply.
	cNewLnk = document.createElement("a");
	cNewLnk.name = cNewLnk.id = "lastreply";
	cNewLnk.style.height = "1px";
	cNewLnk.style.fontSize = "1px";
	cNewLnk.style.color = "#fff";
	cNewLnk.style.textDecoration = "none";
	cNewTxt = document.createTextNode("&nbsp;");
	cNewLnk.appendChild(cNewTxt);
	cDiv.parentNode.insertBefore(cNewLnk, cDiv);
	try {
		hash = window.location.hash.toString();
		if (hash.match(/^#all/)) {
			szURL = window.location.href;
			szURL = szURL.replace(/#all/, "#lastreply");
			window.location.hash = "#lastreply";
			window.setTimeout("window.location.replace = '" + szURL + "'", 240); // This seems to work in Chrome, too.
			if (true === bIsChrome) {
				window.setTimeout("window.location.replace = '" + szURL + "'", 250);
			}
		}
	} catch(e) { }
	// Correct the skip to position if the URL has an fid argument.
	try {
		params = window.location.search.toString();
		if (params.match(/(fid=[^&]+)/)) {
			try {
				cNewTxt = params.match(/(fid=[^&]+)/)[1];
				if (cNewTxt) {
					cNewTxt = cNewTxt.replace(/fid=/, "fid_"); // Convert to thread div's id.
					if (document.getElementById(cNewTxt)) {
						window.location.hash = "#" + cNewTxt;
						szURL = window.location.href;
						window.setTimeout("window.location.replace('" + szURL + "')", 260);
					}
				}
			} catch (e) { }
		}
	} catch(e) { }
}

// Collect all CSS tweaks here.
function tweakForumCSS() {
	if (true === ghfConfig.thread_title_bold) {
		GM_addStyle('td.lcnCSS div:first-child a   { font-weight:bold; }');
		GM_addStyle('td.wltbsCSS div:first-child a { font-weight:normal; }');
		GM_addStyle('td.wltbgCSS div:first-child a { font-weight:normal; }');
	}
	GM_addStyle('.wpcprsCSS { font-size: 0.9em; }');
	GM_addStyle('.wpcprsCSS form { font-size: 1.1em; }');
	GM_addStyle('.wpcprsCSS span.wrwmCSS { padding-right:.3em; }');
	if (1 === ghfConfig.votes_bold) {
		GM_addStyle('.wpcprsCSS { font-weight:bold; }');
		GM_addStyle('.wpcprsCSS a { font-weight:normal; }');
	}
	if (true === ghfConfig.fixed_menu) {
		// z-index fixes the case of less than four entries on overview pages. 
		GM_addStyle('#wparoot { position:fixed; left:3px; top:8.5em; width:165px; z-index: 5; }');
	}

	GM_addStyle('input.wpsfbCSS, input#wbsb_submit, button.wtssbCSS, #wssmfm button { font-weight:normal; font-size:85%; padding:.1em .3em; background:#ddd; border-width:1px; cursor:pointer; }');
	GM_addStyle('#wssmfm { max-height: 32px; }');
	GM_addStyle('#wssmfm select { font-size:85%; }');
	GM_addStyle('#wssmfm option { padding: 1px 2px; min-width: 80px; }');
	GM_addStyle('.wbsb_CSS.wpsfbCSS { margin-right: .7em; }');
	GM_addStyle('.wpcprsCSS { margin-top:6px; padding-top: 2px; border-top: 1px solid #eee; }');
	GM_addStyle('.wpcpraCSS { border-top: 1px solid #d5d5ee; }');
	GM_addStyle('.wpcprsCSS a.lCSS:hover { color:#336; }');
	GM_addStyle('div.goog-custom-button-outer-box { cursor:pointer; font-weight:normal; }');
	GM_addStyle('.wparootCSS li { margin-top: 7px; }');

	GM_addStyle('table.lctCSS td div:first-child a { font-size: ' + ghfConfig.title_font_size + '; }');

	GM_addStyle('td.wltbsCSS { background-color: ' + ghfConfig.color_read_by_you + '; }');
	GM_addStyle('td.wltbsCSS div { background-color: ' + ghfConfig.color_read_by_you + '; }');
	GM_addStyle('td.wltbtcCSS { background-color: ' + ghfConfig.color_read_by_others + '; }');
	GM_addStyle('td.wltbtcCSS div { background-color: ' + ghfConfig.color_read_by_others + '; }');
}

// Inject the left hand main menu for the boards and the "browse all discussions" page.
function injectForumMenuEntries() {
	var forumname = forumkey = szReferrer = params = "", bRefresh = false;
	forumName = getForumName();
	if (!forumName || 0 === forumName.length) {
		return;
	}
	forumKey = forumName + "_" + languageCode;

	var cachedEntries = "", cachedDate = GM_getValue(forumKey + "_cached", ""), currentDate = new Date();
	params = window.location.search.toString();
	if (params.match(/rm=1/)) {
		bRefresh = true;
	}

	if (cachedDate != "" && false === bRefresh) { // Check if cache is still valid (24 hours) or force reload.
		if (currentDate - Date.parse(cachedDate) < (86400 * 1000)) {
			cachedEntries = GM_getValue(forumKey, "");
		}
	}
	if (cachedEntries != "") { 
		showSideLinks(cachedEntries);
	} else { // Fetch board links from the forum homepage.
		var theurl = thedomain + "/support/forum/p/" + forumName + "?hl=" + languageCode;
		var request = new XMLHttpRequest();
		request.onreadystatechange = function() { // Common AJAX.
			if ((request.readyState == 4) && (request.status == 200)) {
				var doc = request.responseText;
				var entries = doc.match(/href=.*?lid=.*?</g);
				var serialized = entries.join("\t");
				GM_setValue(forumKey, serialized); // cache data
				GM_setValue(forumKey + "_cached", currentDate.toString());
				showSideLinks(serialized);
			}
		}
		request.open('GET', theurl, true);
		request.send(null);
	}
}

// Workaround for the Chrome implementation of @include, applies to Firefox, too (today, the regular inclusion works fine).
// This is the core handler with the script execution.
if (0 === URL.indexOf("http://www.google.") && (-1 != URL.indexOf("/support/forum/p/") || 0 < URL.indexOf("/support/forum/user"))) {
	var nPageType = 0;

	// Determine language code.
	languageCode = URL.match(/[\?&]hl=([^&#]*)/)[1];

	// New with R > 72: evaluate page type - (all threads | board overview | my discussions) or thread.
	if (document.getElementById("wlhd")) { // overviews + user profile
	   nPageType = 1;
	} else if (URL.search(/thread\?/)) {
	   nPageType = 2;
	}

	// Read the current user settings.
	setGHFConfig();

	// Inject the CSS styles depending on user settings.
	tweakForumCSS();

	// Applies to all pages. Inject the board links (menu).
	if (true === ghfConfig.show_side_links) {
		injectForumMenuEntries();
	}

	// Applies only to type 1.
	if (1 === nPageType && true === ghfConfig.link_to_last) {
		window.addEventListener("load", addLinkToLastPost, false); 
	}
  
	// Applies only to type 2.
	if (2 === nPageType) {
	   window.addEventListener("load", tweakThread, false);
	}

}

// EOF. 